#ifndef SYMBOL_H_
#   include "symbol.h"
#endif
#ifndef SYMLIST_H_
#   include "symlist.h"
#endif
#define SYMTAB_H_       // ???TODO: temporary hack until symbols.h integrated!
#ifndef GLOBALS_H_
#   include "globals.h"
#endif

/* constants
************************************************************************/    

#define HASH_TABLE_SIZE     (199)       /* use prime number so modulo is decent hash */


/* type definitions
************************************************************************/    

struct  TSymbol;
struct  TSymAtom;

typedef struct  TSymAtom        /* underlying atomic object             */
    {
    TToken      Name;           /* convenience: token of first instance */
    int         BitFlags;
    SYMLIST     Instances;      /* list of all instances of this atom   */
    }           TSymAtom;

typedef struct  TSymbol
    {
    TToken      Name;           /* Token associated with this instance  */
    TSymAtom*   Atom;
    }           TSymbol;

enum
    {
    SYM_TERMINAL                = 0x0001,
    SYM_LEFTRECURSIVE           = 0x0002,
    SYM_FIRSTFOLLOWCLASH        = 0x0004,
    SYM_NULLABLE                = 0x0008,
    SYM_USEDINRULE              = 0x0010,
    SYM_SIMPLE                  = 0x0020,   /* symbol derives only terminals    */
    SYM_EMPTY                   = 0x0040,   /* symbol derives nothing           */
    };

/* file scope variables
************************************************************************/
static  SYMLIST HashTable[HASH_TABLE_SIZE];


/* internal function declarations
************************************************************************/    

static SYMBOL       SymbolNew(TToken Token, int Terminal);
static TSymAtom*    SymAtomFind(TToken Token, int Create);
static void         SymAtomBitSet(TSymAtom* Atom, int Bit);

/* external function definitions
************************************************************************/    

/* SymbolNewNonTerm() - create a new non-terminal data structure.
 *
 * We assume someone else has already verified that no symbol
 * with this name already exists.
 ***********************************************************************/
SYMBOL  SymbolNewNonTerm(TToken Token)
    {
    SYMBOL  Result;

    Result              = SymbolNew(Token, FALSE);
    Globals.NonTerms    = SymListAdd(Globals.NonTerms, Result);

    return Result;
    }

/* SymbolNewTerm() - create a new terminal data structure.
 *
 * We assume someone else has already verified that no symbol
 * with this name already exists.
 ***********************************************************************/
SYMBOL  SymbolNewTerm(TToken Token)
    {
    TSymbol*    Result;

    if(SymbolFind(Token, FALSE))
        ErrorExit(ERROR_TERM_ALREADY_DEFINED, "Terminal already defined: '%.*s'\n",
              Token.TextLen, Token.Text);

    Result  = SymbolAdd(Token, SYM_TERMINAL);
    assert(Result != NULL);


    Globals.Terminals   = SymbolListAdd(Globals.Terminals, Result);

    /* Bit of a hack, but we'll set ->First here.
     * the FIRST() set of a terminal is just the terminal
     */
    Result->First   = SymbolListAdd(Result->First, Result);

    return (SYMBOL)Result;
    }

TSymbol*    SymbolNewTerm(TToken Token)
    {
    TSymbol*    Result;

    if(SymbolFind(Token))
        ErrorExit(ERROR_TERM_ALREADY_DEFINED, "Terminal already defined: '%.*s'\n",
              Token.TextLen, Token.Text);

    Result  = SymbolAdd(Token, SYM_TERMINAL);
    assert(Result != NULL);


    Globals.Terminals   = SymbolListAdd(Globals.Terminals, Result);

    /* Bit of a hack, but we'll set ->First here.
     * the FIRST() set of a terminal is just the terminal
     */
    Result->First   = SymbolListAdd(Result->First, Result);

    return Result;
    }


/* SymbolNew() - return a new symbol instance.
 */
static SYMBOL       SymbolNew(TToken Token, int Terminal)
    {
    TSymbol*    Symbol = NEW(TSymbol);

    assert(Symbol != NULL);
    Symbol->Name    = Token;
    Symbol->Atom    = SymAtomFind(Token, TRUE);
    /* if symbol already existed */
    if(SymListCount(Symbol->Atom->Instances) == 0)
        assert(!Terminal == !SymAtomBitGet(Symbol->Atom, SYM_TERMINAL));
    else if(Terminal)
        SymAtomBitSet(Symbol->Atom, SYM_TERMINAL);

    /* add this new instance to atom's list of instances */
    Symbol->Atom->Instances = SymListPush(Symbol->Atom->Instances, Symbol);

    return (SYMBOL)Symbol;
    }

/* internal functions
************************************************************************/    

/* Hash() - hash a name (from a TToken).
 *
 * A simple XOR hash plus module the prime table size.
 ************************************************************/
static size_t  Hash(const char* Name, size_t Len)
    {
    size_t  Result  = Len;

    while(Len-- > 0)
        Result  = (Result << 5) ^ *Name++;

    return Result % HASH_TABLE_SIZE;
    }


/* SymAtomRef() - create/locate a symbol atom.
 *
 * A TSymAtom contains the symbol attributes that are the same
 * across all instances of that symbol.
 ***************************************************************/
static TSymAtom*    SymAtomFind(TToken Token, int Create)
    {
    size_t      Bucket  = Hash(Token.Text, Token.TextLen);
    TSymAtom*   Atom = NULL;
    SYMLIST     SymList;
    TSymbol*    Instance;

    SymList     = HashTable[Bucket];
    Instance    = (TSymbol*)SymbolListFind(SymList, Token);

    if(Instance)                    /* if atom already exists */
        Atom    = Instance->Atom;
    else if(Create)                 /* if caller wants us to create it */
        {
        Atom    = NEW(TSymAtom);
        assert(Atom);
        Atom->Name  = Token;
        }
    
    return Atom;
    }

static void         SymAtomBitSet(TSymAtom* Atom, int Bit)
    {
    Atom->BitFlags  |= Bit;
    }
